/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2024年7月4日
 * V4.0
 */
package com.jphenix.service.db.util;

import java.sql.Clob;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanFactory;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;

/**
 * 数据源工具类
 * com.jphenix.service.db.util.DBSourceUtil
 * 
 * 注意：这是一个高风险类，操作这个类时很容易忘记关闭数据库连接，
 *      导致连接资源用尽，最终导致宕机，而且遇到这种问题不易排查问题原因.
 *      使用时切记小心谨慎!!
 * 
 * 2024-07-10 新增更新数据方法
 * 2024-07-29 新增查询方法
 * 
 * @author MBG
 * 2024年7月4日
 */
@BeanInfo({"dbSourceUtil","0","","","1"})
@ClassInfo({"2024-07-29 14:28","数据源工具类"})
public class DBSourceUtil extends ABase {
	
	//#region 【变量声明区】
	private Connection        conn     = null; // 当前数据库链接对象
	private PreparedStatement stmt     = null; // 事务对象
	private ResultSet         rs       = null; // 记录集游标对象
	private boolean           isClosed = true; // 是否已关闭
	//#endregion
	
	//#region 【静态】newConnection(beanFactory,driverStr,url,userName,password) 获取新的数据库连接对象
	/**
	 * 获取新的数据库连接对象
	 * @param beanFactory    类加载器（在架构中不能用Class.forName构建，因为jar包不一定在lib文件夹中）
	 * @param driverStr      驱动类路径
	 * @param url            连接URL
	 * @param userName       登录账号
	 * @param password       登录密码
	 * @return               连接类实例
	 * @throws Exception     异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public static Connection newConnection(
			IBeanFactory beanFactory,String driverStr,String url
			,String userName,String password) throws Exception {
		Connection conn = null; // 构建返回值
		try {
		// 构建驱动类实例
		Driver driver = (Driver)beanFactory.getClassLoader().loadClass(driverStr).newInstance();
        Properties info = new Properties(); // 构建入参对象
        info.put("user",userName);
        info.put("password",password);
        conn = driver.connect(url,info);
		}catch(Exception e) {
			e.printStackTrace();
			if(conn!=null) {
				try {
					conn.close();
				}catch(Exception e2) {}
			}
			throw e;
		}
		return conn;
	}
	//#endregion
	
	//#region 【静态】newConnection(jndiName,userName,password) 通过jndi方式获取数据库连接
	/**
	 * 通过jndi方式获取数据库连接
	 * @param jndiName    jndi名
	 * @param userName    登录账号
	 * @param password    登录密码
	 * @return            数据库连接
	 * @throws Exception  异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public static Connection newConnection(String jndiName,String userName,String password) throws Exception {
		Connection conn = null; // 构建返回值
		try {
			//构建JNDI上线文
			Context ic = new InitialContext();
			//获取数据源
			DataSource ds = (DataSource)ic.lookup(jndiName);
			if(userName!=null && userName.length()>0) {
				conn = ds.getConnection(userName,password);
			}else {
				conn = ds.getConnection();
			}
		}catch(Exception e) {
			e.printStackTrace();
			if(conn!=null) {
				try {
					conn.close();
				}catch(Exception e2) {}
			}
			throw e;
		}
		return conn;
	}
	//#endregion

	//#region connect(jndiName,userName,password) 建立数据库连接
	/**
	 * 建立数据库连接
	 * @param jndiName    jndi的名字
	 * @param userName    登录账号
	 * @param password    登录密码
	 * @throws Exception  异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public void connect(String jndiName,String userName,String password) throws Exception {
		try {
			conn = newConnection(jndiName,userName,password);
			isClosed = false;
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region connect(driverStr,url,userName,password) 建立数据库连接
	/**
	 * 建立数据库连接
	 * @param driverStr    驱动类路径
	 * @param url          jdbc的URL
	 * @param userName     登录账号
	 * @param password     登录密码
	 * @throws Exception   异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public void connect(String driverStr,String url,String userName,String password) throws Exception {
		try {
			conn = newConnection(getBeanFactory(),driverStr,url,userName,password);
			isClosed = false;
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region query(sql) 执行查询
	/**
	 * 执行查询
	 * @param sql         查询语句
	 * @throws Exception  异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public void query(String sql) throws Exception {
		query(sql,null);
	}
	//#endregion
	
	//#region query(infos) 执行查询
	/**
	 * 执行查询
	 * @param infos       采用脚本方式 <%S  S%> 
	 * @throws Exception  异常
	 * 2024年7月29日
	 * @author MBG
	 */
	@SuppressWarnings("unchecked")
	public void query(Object[] infos) throws Exception {
		Object[] fixInfos = DBUtil.fixSqlInfo(log,infos);     // 整理传入值
		query((String)fixInfos[0],(List<String>)fixInfos[1]); // 执行查询
	}
	//#endregion
	
	//#region query(sql,params) 执行查询
	/**
	 * 执行查询
	 * @param sql         查询语句
	 * @param params      语句中需要设置的值
	 * @throws Exception  异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public void query(String sql,List<String> params) throws Exception {
		if(isClosed) {
			throw new Exception("--Error-- The Connection is closed");
		}
		// 日志信息
		StringBuffer logSbf = new StringBuffer();
		try {
			logSbf.append("--Begin Query SQL:\n").append(sql).append("\n");
			
			stmt = conn.prepareStatement(
					sql
					,ResultSet.TYPE_SCROLL_INSENSITIVE
					,ResultSet.CONCUR_READ_ONLY);
			
			if(params!=null) {
				String value;
				for(int i=0;i<params.size();i++) {
					value = params.get(i);
					if(value==null) {
						stmt.setNull(i+1, Types.VARCHAR);
						logSbf.append("+++Set Property"+(i+1)+":{NULL}\n");
					}else {
						stmt.setString(i+1, value);
						logSbf.append("+++Set Property"+(i+1)+":["+value+"]\n");
					}
				}
			}
			
			log.sqlLog(logSbf); // 输出日志
			
			rs = stmt.executeQuery();// 执行查询
			
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region getQueryFields() 获取查询出的字段名序列
	/**
	 * 获取查询出的字段名序列
	 * @return             查询出的字段名序列
	 * @throws Exception   异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public List<String> getQueryFields() throws Exception {
		// 构建返回值
		List<String> fields = new ArrayList<>();
		if(isClosed) {
			return fields;
		}
		try {
			ResultSetMetaData metaData = rs.getMetaData();
			if(metaData!=null) {
				//获取字段数量
				int colCountInt = metaData.getColumnCount();
				String fieldName;
				for(int i=0;i<colCountInt;i++) {
					fieldName = metaData.getColumnLabel(i+1);
					if(fieldName==null || fieldName.length()<1) {
						fieldName = metaData.getColumnName(i+1);
					}
					fields.add(fieldName);
				}
			}
			return fields;
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion

	//#region get(rowCount) 执行查询并返回记录集
	/**
	 * 执行查询并返回记录集
	 * @param rowCount     获取记录数
	 * @return             记录集
	 * @throws Exception   异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public List<Map<String,String>> get(int rowCount) throws Exception {
		return get(1,rowCount);
	}
	//#endregion
	
	//#region get(fromRow,rowCount) 执行查询并返回记录集
	/**
	 * 执行查询并返回记录集
	 * @param fromRow    起始行号 从1开始
	 * @param rowCount   行数
	 * @return           记录集
	 * @throws Exception 异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public List<Map<String,String>> get(int fromRow,int rowCount) throws Exception {
		// 构建返回值
		List<Map<String,String>> reList = new ArrayList<>();
		if(isClosed) {
			return reList;
		}
		fromRow--;
		if(fromRow>0) {
			if(!rs.absolute(fromRow)) {
				close();
				return reList;
			}
		}else {
			if(!rs.next()) {
				close();
				return reList;
			}
		}
		// 获取查询字段名序列
		List<String> fields = getQueryFields();
		for(int i=0;i<rowCount;i++) {
			reList.add(get(fields));
			if(!rs.next()) {
				close();
				return reList;
			}
		}
		return reList;
	}
	//#endregion
	
	//#region get(fields) 获取整行记录数据
	/**
	 * 获取整行记录数据
	 * @param  fields    字段名序列
	 * @return           整行记录数据
	 * @throws Exception
	 * 2024年7月4日
	 * @author MBG
	 */
	public Map<String,String> get(List<String> fields) throws Exception {
		// 构建返回值
		Map<String,String> data = new LinkedHashMap<>();
		if(isClosed) {
			return data;
		}
		if(fields==null) {
			fields = getQueryFields();
		}
		try {
			Object valueObj;
			for(String field:fields) {
				valueObj = rs.getObject(field);
				if (valueObj instanceof Clob) {
					data.put(field,DBUtil.getClobString(((Clob)valueObj)));
				}else if(valueObj instanceof byte[]) {
					//mysql 在语句中使用函数时，有时会将本应该返回字符串的值返回字节数组
					data.put(field,new String((byte[])valueObj));
				}else {
					if(valueObj==null){
						data.put(field,"");
					}else {
						data.put(field,valueObj.toString());
					}
				}
			}
			return data;
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region get(columnName) 获取指定字段值
	/**
	 * 获取指定字段值
	 * @param columnName 字段名
	 * @return           字段值
	 * @throws Exception 异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public String get(String columnName) throws Exception {
		if(isClosed) {
			return "";
		}
		try {
			// 获取字段值
			Object valueObj = rs.getObject(columnName);
			if (valueObj instanceof Clob) {
				return DBUtil.getClobString(((Clob)valueObj));
			}else if(valueObj instanceof byte[]) {
				//mysql 在语句中使用函数时，有时会将本应该返回字符串的值返回字节数组
				return new String((byte[])valueObj);
			}else {
				if(valueObj==null){
					return "";
				}
				return valueObj.toString();
			}
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region next() 游标指向下一行
	/**
	 * 游标指向下一行
	 * @return            是否操作成功
	 * @throws Exception  异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public boolean next() throws Exception {
		if(isClosed) {
			return false;
		}
		try {
			if(rs.next()) {
				return true;
			}
			close();
			return false;
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region absolute(row) 跳过指定行数
	/**
	 * 跳过指定行数
	 * @param row         指定行数
	 * @return            是否跳过成功
	 * @throws Exception  异常
	 * 2024年7月4日
	 * @author MBG
	 */
	public boolean absolute(int row) throws Exception {
		if(isClosed) {
			return false;
		}
		try {
			if(rs.absolute(row)) {
				return true;
			}
			close();
			return false;
		}catch(Exception e) {
			e.printStackTrace();
			close();
			throw e;
		}
	}
	//#endregion
	
	//#region close() 关闭全部数据库对象
	/**
	 * 关闭全部数据库对象
	 * 2024年7月4日
	 * @author MBG
	 */
	public void close() {
		if(isClosed) {
			return;
		}
		isClosed = true;
		if(rs!=null) {
			try {
				rs.close();
			}catch(Exception e) {}
		}
		if(stmt!=null) {
			try {
				stmt.close();
			}catch(Exception e) {}
		}
		if(conn!=null) {
			try {
				conn.close();
			}catch(Exception e) {}
		}
	}
	//#endregion

	//#region execute(infos) 执行更新语句
	/**
	 * 执行更新语句
	 * @param infos       采用脚本方式 <%S  S%> 
	 * @return            更新记录数
	 * @throws Exception  异常
	 * 2024年7月10日
	 * @author MBG
	 */
	@SuppressWarnings("unchecked")
	public int execute(Object[] infos) throws Exception {
		Object[] fixInfos = DBUtil.fixSqlInfo(log,infos); // 整理传入值
		return execute((String)fixInfos[0],(List<String>)fixInfos[1]);
	}
	//#endregion
	
	//#region execute(sql) 执行更新语句
	/**
	 * 执行更新语句
	 * @param sql        更新语句
	 * @return           更新记录数
	 * @throws Exception 异常
	 * 2024年7月10日
	 * @author MBG
	 */
	public int execute(String sql) throws Exception {
		return execute(sql,null);
	}
	//#endregion
	
	//#region execute(sql,uLit) 执行更新语句
	/**
	 * 执行更新语句
	 * @param sql         需要执行的sql语句
	 * @param uList       需要更新的数据序列
	 * @return            更新记录数
	 * @throws Exception  异常
	 * 2024年7月10日
	 * @author MBG
	 */
	public int execute(String sql,List<String> uList) throws Exception {
		PreparedStatement stmt     = null;                         // 数据库事务对象
		Integer           timeOut  = null;                         // 查询超时时间（秒）
		try {
			sql  = DBUtil.fixSqlWithPara(sql,uList); // 处理语句中的 (#)关键字
			stmt = conn.prepareStatement(sql);
			
			String parasLog = DBUtil.fixPreparedStatement(stmt, uList); // 设置值
			long beforeTime = System.currentTimeMillis(); //记录执行开始时间
			// 查询超时时间（秒）
			timeOut = sint(ThreadSession.get("DB_QUERY_TIME_OUT"));
			if(timeOut>0) {
				stmt.setQueryTimeout(timeOut);
			}
			int res =  stmt.executeUpdate(); //执行脚本并获取返回值
			log.sqlLog("ExecuteUpdate Res:["+res+"]  timeOut:["+timeOut+"] Use Time:[" 
					+ SDate.showMillisecond(System.currentTimeMillis()-beforeTime)+ "] SQL:[\n"+sql+"\n] "+parasLog);
			conn.commit();
			return res;
		} catch (Exception e) {
			e.printStackTrace();
			log.error("executeUpdate Exception Sql:[\n"+sql+"\n] uList:["+DebugUtil.getListValue(uList," ")+"]",e);
			try {
				//执行回滚
				conn.rollback();
			}catch(Exception e2) {}
			throw e;
		} finally {
			try {
				stmt.close();
			}catch(Exception e1) {}
			stmt = null;
		}
	}
	//#endregion
}



















